
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Globalization;

namespace Normal.Lang
{
	public delegate void ScannerMacro();

	// Performs Lexical Analysis on the source code. This object 
	// takes the source code text as input and generates a 
	// stream of tokens as output.
	//
	// source text -> Scanner -> token stream.
	
	public class Scanner
	{
		internal ScanReader sr;
		internal Hashtable macros;
		
		public Scanner(TextReader r)
		{
			InitializeMacros();
			sr = new ScanReader(r); 
		}
		
		internal void InitializeMacros()
		{
			macros = new Hashtable();
			macros.Add('#', new ScannerMacro(SkipComment));
		}

		// Extract all tokens from the source code stream.
		// This method drives the NextTokens method.
		
		public ArrayList Scan()
		{
			ArrayList tokens = new ArrayList();
			ArrayList nexttokens; 
			
			while ((nexttokens = NextTokens()) != null)
			{
				tokens.AddRange(nexttokens);
			}
			
			return tokens;
		}

		// Extract the next tokens from the source code stream.
		// May return multiple tokens per invocation. Returns 
		// null if the EOF is reached.
		
		public ArrayList NextTokens()
		{
			ArrayList tokens = new ArrayList();
							
			Int32 c = sr.Read(); 
			while (Char.IsWhiteSpace((Char) c))
			{
				c = sr.Read();
			}

			switch (c) 
			{
				case -1:
					return null;
					
				case '(':
					tokens.Add(new Token(TK.PAREN_OPEN, sr));
					break;

				case ')':
					tokens.Add(new Token(TK.PAREN_CLOSE, sr));
					break;
					
				case '"':
					Token token = new Token(TK.STRING, sr);
					token.data = ReadString();
					tokens.Add(token);
					break;
					
				default:
					ScannerMacro macro = (ScannerMacro) macros[(char) c];
					
					if (macro != null)
						macro();
					else
					{						
						String s = ((Char) c) + ReadSymbol();
						
						Token t = new Token(sr);					
						Double d;
						
						if (Char.IsLetter((Char) c))
						{
							t.type = TK.SYMBOL;
							t.data = s;						
						}
						else if (Double.TryParse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out d))
                        {
							t.type = TK.INTEGER;
							t.data = (Int32) d;
                        }
                		else if (Double.TryParse(s, NumberStyles.Float, NumberFormatInfo.CurrentInfo, out d))
                        {
							t.type = TK.FLOAT;
							t.data = d;
                        }
                        else
                        {
                        	// Handles symbols starting with a 
                        	// digit.
                        	
							t.type = TK.SYMBOL;
							t.data = s;
                        }

						tokens.Add(t);
					}					
					break;
			}
			
			return tokens;
		}
		
		// Skip a single line comment (starts with '#')
		
		internal void SkipComment()
		{
			Int32 c = sr.Peek();
            while (c != -1 && c != '\n' && c != '\r')
            {
				sr.Read();
                c = sr.Peek();
            }
			if (c != -1 && c != '\n' && c != '\r')
			    sr.Read();
		}		

		// Skip a multi line comment (starts with '#')
		
		internal void SkipMultiLineComment()
		{
			Int32 c = sr.Peek();
            while (c != -1 && c != '\n' && c != '\r')
            {
				sr.Read();
                c = sr.Peek();
            }
			if (c != -1 && c != '\n' && c != '\r')
			    sr.Read();
		}

		internal String ReadSymbol()
		{
			Int32 c;
			StringBuilder b = new StringBuilder();
			
			c = sr.Peek();
			while (IsSymbolChar((Char) c))
			{
				b.Append((Char) sr.Read());
				c = sr.Peek();
			}

			return b.ToString();
		}
		
		internal String ReadString()
		{
			Int32 c;
			StringBuilder b = new StringBuilder();

			while ((c = sr.Read()) != '"')
			{
				b.Append((Char) c);
			}

			return b.ToString();
		}
		
		// Helper method, returns true if the character is valid
		// for a symbol.
		
		internal bool IsSymbolChar(Char c)
		{
			return (!Char.IsWhiteSpace(c)) && c != ')' && c != '(';
		}
	}	
	
	// A specialized TextReader for source code parsing.
	
	public class ScanReader
	{
		TextReader reader;
		public int line;
		public int column;
		
		public ScanReader(TextReader reader)
		{
			this.reader = reader;
			this.line = 1;
			this.column = 0;
		}
		
		public Int32 Read()
		{
			Int32 c = reader.Read();
			++column;
			
			if (c == '\n') 
			{
				++line;
				column = 0;
			}
			return c;
		}
		
		public Int32 Peek()
		{
			return reader.Peek();
		}		
	}	
}

/*
TODO: 

keep row in Reader
reader gets string.
*/